# Copyright (c) 2011, Chandler Armstrong (omni dot armstrong at gmail dot com)
# see LICENSE.txt for details




"""
vizier is a game world and sprite image management library.  vizier offers two
features: 1) image scrolling, zooming, and rotating transformation processes and
2) 2d frustum culling.

vizier facilitates image transformation by offering scrolling and zooming
methods for the world image, and rotating methods for sprites.  vizier manages
the images to ensure the scrolling and zooming is properly applied, relieving
client code of responsilibity for repositioning and scaling images.  the
vizier.Sprite class keeps a cache of and manages all the various images that
sprites generate for rotate operations.

vizier provides 2d frustum culling for managing very large game worlds.  a
frustum loads only that portion of the world within the frustum.  very large
worlds challenge game design with memory and precision burdens; frustum culling
eliminates these challenges.  vizier loads and evicts portions of the world
from the frustum, using client supplied load and evict size parameters.  see

Bilas, Scott. (2003). The Continuous World of Dungeon Seige. In Proceedings of
the Game Developer's Conference 2003.

for more information of frustum culling as it is implemented by vizier.

vizier manages all sprites, ensuring sprites are loaded and evicted along with
the portions of the world.  evicted sprites are frozen but reactivated
automatically when the portion of the world they are upon becomes added to the
frustum once again.


CONCEPTS:
the game world-
the game world is all the images and data that compose the game world, and how
these things are stitched together.  the game world is divided into nodes, and
each node is loaded separately by vizier.  vizier will load a node if it is
within the frustum.  if a node is already loaded and exits the frustum, vizier
will evict it.  sprites are evicted and loaded along with the nodes they are
upon.

node-
a node is a small portion of the world that can be loaded and evicted
independently of other nodes.  a node is essentially a rect object, possessing
a size attribute. an image, at minimum, is associated with each node and will be
loaded by vizier to be blitted to the screen.  data may optionally be associated
with nodes that will be used to define the presence of tiles on the node.  a
graph stores nodes and their associated values and defines the spatial
relationship between nodes.

the graph-
the graph defines node topology.  the graph must be a networkx directed edge
graph.  the graph defines topology as edges between nodes.  each edge between
nodes, hereafter called 'source' and 'target' node, must define an 'x' and 'y'
attribute.  the x and y attributes are the offsets of the topleft of the target
node from the topleft of the source node.  each node must define an attribute
named 'img' that is the path to the node's img on the hard drive. an optional
attribute named 'data', may also be defined that is the path to the node's tile
data on the hard drive.  graphs are typically defined in source files loaded
from the hard disk. vizier uses the graphml format to define networkx graph
objects; see

<http://networkx.lanl.gov/index.html>

for details on networkx and see

<http://graphml.graphdrawing.org/>

for details on graphml.  see example 1 for a minimal example of a graphml graph
defining a proper graph object for vizier.


EXAMPLE 1--
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	 xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
			     http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">

  <key id="data" for="node" attr.name="data" attr.type="string" />
  <key id="img" for="node" attr.name="img" attr.type="string" />
  <key id="x" for="edge" attr.name="x" attr.type="int" />
  <key id="y" for="edge" attr.name="y" attr.type="int" />

  <graph id="example" edgedefault="directed">

    <!-- nodes -->

    <node id="0">
      <data key="data">./resources/nodes/node0.node</data>
      <data key="img">./resources/img/img0.tga</data>
    </node>
    <node id="1">
      <data key="data">./resources/nodes/node1.node</data>
      <data key="img">./resources/img/img1.tga</data>
    </node>

    <!-- edges -->

    <edge source="0" target="1">
      <data key="x">270</data>
      <data key="y">0</data>
    </edge>

  </graph>
</graphml>


node data-
node data defines tiles upon a node.  node data defines the number of columns
and rows of tiles and the size of each tile and associates a value with each
tile.  the number of rows and columns and the size of the tiles must be such
that the tiles exactly fit the size of the image associated with the node.  note
that tiles may be only square.  the tile value is entirely client supplied and
used; vizier will simply store and return the value type of each tile when
queried for the tile at a position on a node.  node data is supplied in a text
file.  the first three lines of the file are for header data and the rest of the
file is for the node data.  the first line of the header data is the number of
columns, the second line is the number of rows.  the third and list line is the
size of each tile in pixels.  the line immediately following the header data
begins the node data.  each line represents a row of tiles.  each tile is
represented by a white-space delimited value.  the line is, obviously,
terminated by a newline.  vizier does not specify any particular extension for
node data files, but it is suggested to use '.node', as this extension does not
appear to be utilized by any other major application.  see example 2 for a
minimal example of a node data file.


EXAMPLE 2--
ncols 9
nrows 9
cellsize 30
1 2 1 2 3 2 1 2 1
2 1 2 1 2 1 2 1 2
1 2 1 2 1 2 1 2 1
2 1 2 1 2 1 2 1 2
1 2 1 2 1 2 1 2 1
2 1 2 1 2 1 2 1 2
1 2 1 2 1 2 1 2 1
2 1 2 1 2 1 2 1 2
1 2 1 2 1 2 1 2 1


frustum-
a frustum loads the nodes of the world as they enter the frustum.  the frustum
is essentially a rect, and nodes are checked to see if they intersect the rect.
nodes that intersect the rect are loaded to the frustum, their images will be
blitted to the frustum surface, and any sprites on the nodes will be updated
whever the viewer update method is called.

culling frustum-
the culling frustum evicts nodes of the world that are not within the culling
frustum.  the culling frustum works much the same way as the frustum, and is
essentially a rect.  the culling frustum must be at least as big as the frustum,
and can be much larger, permitting loaded nodes of the world to remain loaded
even when they are no longer in the frustum, as long as they are within the
culling frustum.

world space-
world space is the position of the nodes and sprites on the frustum.  world
space is the only space that is common for all sprites and nodes in the frustum.
however, world space changes each time the frustum is moved, so world space can
be assumed to be valid for only a single frame.  the frustum is always centered on
the viewer's rect, so that the viewer rect maps directly to world space.

node space-
node space is simply the rect and id of the node.  node space can not be assumed
to be common between sprites or other objects and all things must be converted
to world space before processing interactions.  however, only node space is
constant, so all objects must always update and store their node space.

sprites-
sprites, as with nodes, are loaded to and evicted from the frustum.  sprite
eviction is not enacted directly by the frustum, rather when the node where the
sprite is positioned has been evicted, the sprite is also evicted.  evicted
sprites are not active, and are stored by the viewer.  the viewer will check if
a node containing an evicted sprite has become active each time sprites are
updated, and will resume updating sprites when the node where the sprite is
positioned is active once again.  the viewer uses the center of the sprite's
rect to determine the position and node of the sprite.  note that for very large
sprites, or very small culling frustums, it is possible that the node where the
sprite is positioned becomes evicted, and the sprite becomes inactive, while the
sprite is still visible on the viewer. to avoid visible sprites being evicted,
ensure the culling frustum is large enough to accommodate all sprites that may
be used.


USAGE:

manager-
the manager is responsible for instantiating initial sprite images and caching
the images produced by rotation operations on the sprite images.  the manager
will hash each sprite to a key and provide this key to client code.  client code
must use the associated key to instantiate the desired sprite.

sprites-
only vizier sprites can be used with the vizier viewer.  all custom sprites must
subclass vizier.Sprite and must not overwrite any of the attributes or methods.
vizier.Sprite already subclasses pygame.sprite.Sprite and also provides image
and rect attributes; thus vizier sprites are usable in pygame.sprite.Group.

client code should typically work with sprite between the update0 and update1
call.  following update0 the sprite's rect position is current; the rect
position represents the sprite's position in a world space common to anything
that is updated by the viewer.  note that the rect is never scaled nor should it
be.  even when the viewer is zoomed in, world space remains unscaled, and so
should sprites.  the scaling operation is only done once, on the entire frustum
surface.

the vzrect and the node attributes should be treated as a single attribute;
anytime one is changed care must be take to appropriately modify the other.
client code should rarely need to set the vzrect or node attributes.  the viewer
will handle updating the vzrect from the rect automatically, so client code can
typically simply modify the rect between the update0 and update1 call.  an
example of when client code may want to set the vzrect and node attributes is if
a sprite or other game object is being moved to a predetermined node and it is
simpler to work with and store nodes and associated vzrects.

viewer-
the viewer manages sprite and the surface image and ensures everything is
appropriately scaled and positioned.  the viewer is used to scroll and zoom the
surface and update and draw the sprites; it is client code's primary interface
to the game world and sprites.

the viewer freezes sprites that have been evicted from the frustum, and updates
sprites currently on the frustum.  all sprites, once instantiated, must be added
to the viewer's group_evict, the viewer will determine if any sprites in
group_evict should be added to group_active before the next update procedure.

the viewer methods have some restraint on the order in that they must be called.
the correct order is as follows:

scroll(x, y)
update0()
# client code must perform sprite interactions here
update1()
surface_clear = draw()
zoom() # zoom may be called anytime prior blitting the viewer surface
surface.blit(viewer.surf, viewer.rect, area=viewer.area)
clear(surface_clear)
display.update()

reading the viewer surface, with viewer.surf, returns a scaled copy of the
surface.  this is the only time vizier does any scaling, and the scaling is
applied only to the image, never any rects.

checking for collisions and other sprite interactions-
client code should check for collisions and other sprite interactions between
the update0 and update1 call.  between these calls the sprite positions in world
space, the sprite rect attribute, is current.  at this time client code can
modify and set the rect attribute.  the update1 call will ensure the sprite rect
is correctly converted to node space.  note that sprite rects are never scaled
nor should they be.  even when the viewer is zoomed in, world space remains
unscaled, and so should sprites.  the scaling operation is only done once, on
the entire frustum surface.

the most typical method to check for collisions is to iterate through the active
sprite group:

for spr in viewer.group_active:
    #check for collisions...


TROUBLESHOOTING:
Q: vizier is not drawing my sprites.
A: ensure your sprite's node ids are strings, not integers.  vizier reads node
   ids from the node graph as strings.

Q: when my application first starts, no sprites are drawn to the viewer surface
A: vizier checks if it needs to active sprites only when it is scrolled.  call
   the scroll method with zeros for the arguments to get it to sort your sprites
   into the active and evicted groups.

Q: vizier does not clear its old node images, and I cannot clear them by filling
   the SCREEN object, that returned by display.set_mode.
A: vizier does indeed not clear its old node images.  if client code scrolls
   the viewer area over the border of the nodes, then that typical uncleared
   image pattern will occur.  vizier development considered allowing vizier to
   clear its old node images, but in the end decided that client code should
   prevent scrolling the viewer area across the borders of nodes.  client code
   cannot easily clear old nodes for vizier, because it is the actual vizier
   surface, not the display or some other client controlled surface, that would
   need to be cleared.

Q: vizier throws an error if the viewer scrolls off the nodes.
A: vizier does indeed throw an error if the viewer scrolls of the nodes.  the
   error occurs because their are no nodes to place the viewer upon.  vizier
   will not prevent client code from scrolling off the nodes.  client code must
   take precautions to ensure scrolling of the nodes does not occur.

Q: i have placed walls to stop sprites from wandering off the edge of the world,
   but sometimes my sprites ignore them.
A: the walls are probably centered in an inactive node, so they are not in the
   active sprite group.  if client code iterates through the active sprite group
   only, as it should be, it will not see the inactive wall sprites.  the only
   robust solution is to make a wall for each node.
"""

from ._viewer import Main as Viewer
